#include "UdpSocket.hpp"

using namespace std;



Network::UdpSocket::UdpSocket() throw(SocketException)
{
	_data = new Data;
	_data->instance = 1;
	_data->sock = ::socket(AF_INET6, SOCK_DGRAM, 0);
	_data->localAddr = NULL;
	
	if(_data->sock == INVALID_SOCKET)
		SocketException(lastError());
}



Network::UdpSocket::UdpSocket(const Network::UdpSocket& other)
 : _data(other._data)
{
	++_data->instance;
}



Network::UdpSocket::~UdpSocket()
{
    if(_data->instance == 1)
    {
    	finish();
    	
    	if(_data->localAddr != NULL)
			delete _data->localAddr;
		
		delete _data;
    }
    else
    {
    	--_data->instance;
    }
}


/*
void Network::TcpSocket::bind(unsigned short port) throw(SocketException)
{
    memset(&_sockaddr, 0, sizeof(_sockaddr));
    _sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);        // Adresse IP automatique
    _sockaddr.sin_family = AF_INET;                       // Protocole IP
    _sockaddr.sin_port = htons(port);                     // Port du serveur
    _socklen = sizeof(_sockaddr);
    
    
    if(::bind(_sock, (SOCKADDR*)&_sockaddr, _socklen) < 0)
        throw SocketException(lastError());
}
*/


void Network::UdpSocket::bind(const IpEndPoint& host) throw(SocketException)
{
	if(::bind(_data->sock, host.sockaddr(), host.sockaddrSize()) < 0)
		throw SocketException(lastError());
	
	if(_data->localAddr != NULL)
		delete _data->localAddr;
	
	_data->localAddr = new IpEndPoint(host);
}



void Network::UdpSocket::sendto(const IpEndPoint& endPoint, const void* data, size_t len, int flags) throw(SocketException)
{
	const SOCKADDR* addr = endPoint.sockaddr();
	size_t addrlen = endPoint.sockaddrSize();
	
	#ifdef _WIN32
		int res = ::sendto(_data->sock, static_cast<const char*>(data), len, flags, addr, addrlen);
	#else
		int res = ::sendto(_data->sock, data, len, flags | MSG_NOSIGNAL, addr, addrlen);
	#endif
	
	if(res < 0)
		throw SocketException(lastError());
	
	if((size_t)res != len)
		throw SocketException("Just a part of the data has send");
	
	if(_data->localAddr == NULL)
	{
		SOCKADDR_IN6 addr;
		socklen_t len = sizeof(addr);
		
		if(getsockname(_data->sock, (SOCKADDR*)&addr, &len) >= 0)
			_data->localAddr = new IpEndPoint((SOCKADDR*)&addr);
	}
}



int Network::UdpSocket::recvfrom(IpEndPoint& endPoint, void* data, size_t len, int flags) throw(SocketException)
{
	SOCKADDR addr;
	socklen_t addrlen;
	
	#ifdef _WIN32
		int read = ::recvfrom(_data->sock, static_cast<char*>(data), len, flags, &addr, &addrlen);
	#else
		int read = ::recvfrom(_data->sock, data, len, flags | MSG_NOSIGNAL, &addr, &addrlen);
	#endif

	if(read < 0)
		throw SocketException(lastError());
	
	endPoint = IpEndPoint(&addr);
	
	if(_data->localAddr == NULL)
	{
		SOCKADDR_IN6 addr;
		socklen_t len = sizeof(addr);
		
		if(getsockname(_data->sock, (SOCKADDR*)&addr, &len) >= 0)
			_data->localAddr = new IpEndPoint((SOCKADDR*)&addr);
	}
	
	return (int)read;
}



void Network::UdpSocket::finish()
{
	::shutdown(_data->sock, 2);
    
    #ifdef _WIN32
		::closesocket(_data->sock);
	#else
		::close(_data->sock);
	#endif

	_data->sock = INVALID_SOCKET;
}



long Network::UdpSocket::available() throw(SocketException)
{
    #ifdef _WIN32
        DWORD nbBytes = 0;
        
        if(ioctlsocket(_data->sock, FIONREAD, &nbBytes) != 0)
            throw SocketException(lastError());
        
        return (long)nbBytes;
    #else
        size_t nbBytes = 0;
        
        if(ioctl(_data->sock, FIONREAD, (char*)&nbBytes) < 0)
            throw SocketException(lastError());
        
        return (long)nbBytes;
    #endif
}



const Network::IpEndPoint& Network::UdpSocket::localEndPoint() const throw(SocketException)
{
	if(_data->localAddr != NULL)
		return *_data->localAddr;
	else
		throw SocketException("Unable to find local end point of the socket");
}



Network::UdpSocket& Network::UdpSocket::operator=(const Network::UdpSocket& other)
{
	if(&other != this)
	{
		if(_data->instance == 1)
		{
			finish();
			
			if(_data->localAddr != NULL)
				delete _data->localAddr;
			
			delete _data;
		}
		else
		{
			--_data->instance;
		}
		
		_data = other._data;
		++_data->instance;
    }
	
	return *this;
}



string Network::UdpSocket::lastError()
{
    #ifdef _WIN32
        char buff[256] = {'\0'};
        int error = WSAGetLastError();
        FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM,
                        NULL, error, 0,
                        buff, 256,
                        NULL);
        return string(buff);
    #else
        return string(strerror(errno));
    #endif
}




